using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Mud.Common.Communication;
using System.IO;

namespace Mud.Common
{
    /// <summary>
    /// Class used for logging inside the mud (used instead of Trace).
    /// </summary>
	public sealed class Logger
	{

        /// <summary>
        /// List of log sinks.
        /// </summary>
		private static IList<ILogSink> _sinks = new List<ILogSink>();

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
		public static void Log(LogType logType, string source, string message) {
			if (string.IsNullOrEmpty(message))
				throw new ArgumentNullException("message");
            LogImplementation(logType, source, message);
		}


        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
        /// <param name="arguments">Positional arguments to the message (same as for String.Format).</param>
        public static void Log(LogType logType, string source, string message, params object[] arguments)
        {
			if (string.IsNullOrEmpty(message))
				throw new ArgumentNullException("message");
            LogImplementation(logType, source, message, arguments);
		}



        /// <summary>
        /// Adds the log sink.
        /// </summary>
        /// <param name="sink">The sink.</param>
		public static void AddSink(ILogSink sink) {
			if (sink == null)
				throw new ArgumentNullException("sink");
			lock (typeof(Logger)) {
				if (_sinks.Contains(sink))
					return;
				sink.OnSinkAdded();
				_sinks.Add(sink);
			}
		}

        /// <summary>
        /// Removes the log sink.
        /// </summary>
        /// <param name="sink">The sink.</param>
		public static void RemoveSink(ILogSink sink) {
			if (sink == null)
				throw new ArgumentNullException("sink");
			lock (typeof(Logger)) {
				if (!_sinks.Contains(sink))
					throw new ArgumentException("Sink is not in collection of current Log sinks.");
				_sinks.Remove(sink);
				try {
					sink.OnSinkRemoved();
				} catch (Exception) { }
			}
		}

        /// <summary>
        /// Removes all sinks.
        /// </summary>
		public static void ClearSinks() {
			lock (typeof(Logger)) {
				_sinks.Clear();
			}
		}

        /// <summary>
        /// This is where the actual work is done.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
        private static void LogImplementation(LogType logType, string source, string message, params object[] arguments)
        {
            string formatedMessage = null;
			lock (typeof(Logger)) {
				for (int i = 0; i < _sinks.Count; i++) {
					try {
                        if ((_sinks[i].Filter & logType.Severity) != 0)
                        {
                            if (formatedMessage == null)
                            {
                                if (arguments == null || arguments.Length == 0)
                                    formatedMessage = message;
                                else
                                    formatedMessage = string.Format(message, arguments);
                            }
                            _sinks[i].Log(logType, source, formatedMessage);
                        }
					} catch (Exception) {
						RemoveSink(_sinks[i]);
						i--;
					}
				}
			}
		}


        /// <summary>
        /// Formats the message for display.
        /// Can be used by log sinks to provide consistent log display.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
        /// <returns>Formated string.</returns>
		public static string Format(LogType logType, string source, string message) {
			return string.Format("{0} {4}| {1} | {2} | {3}", TimeFormater.FormatDateTime(DateTime.Now), logType.Name.PadRight(4, ' '), source.PadRight(40, ' '), message, System.Threading.Thread.CurrentThread.ManagedThreadId);
		}

	}


    /// <summary>
    /// Represents log type.
    /// </summary>
	public class LogType {

        /// <summary>
        /// Gets the name of the log type.
        /// </summary>
		public string Name {
			get { return _name; }
		}
		private string _name;

        /// <summary>
        /// Gets the severity of the log message that uses this LogType.
        /// </summary>
		public LogSeverity Severity {
			get { return _severity; }
		}
		private LogSeverity _severity;


        /// <summary>
        /// Initializes a new instance of the <see cref="T:LogType"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="severity">The severity.</param>
		public LogType(string name, LogSeverity severity) {
			if (string.IsNullOrEmpty(name))
				throw new ArgumentNullException("logType");

			_name = name;
			_severity = severity;			
		}


        /// <summary>
        /// Represents the severity of the message.
        /// </summary>
		[Flags]
		public enum LogSeverity
		{
            /// <summary>
            /// No severity, unused except for filtering.
            /// </summary>
			None = 0,
            /// <summary>
            /// Information message.
            /// </summary>
			Information = 1,
            /// <summary>
            /// Warning message.
            /// </summary>
			Warning = 2,
            /// <summary>
            /// Error message.
            /// </summary>
			Error = 4,
            /// <summary>
            /// Debug message.
            /// </summary>
			Debug1 = 8,
            /// <summary>
            /// Debug message but less important then Debug1.
            /// </summary>
			Debug2 = 16,
            /// <summary>
            /// All severities, unused except for filtering.
            /// </summary>
			All = (Information | Warning | Error| Debug1 | Debug2)
		}

		#region Common log types

        /// <summary>
        /// Standard Info log type.
        /// </summary>
		public static LogType Info {
			get { return _info; }
		}
		private static LogType _info = new LogType("Info", LogSeverity.Information);

        /// <summary>
        /// Standard Warning log type.
        /// </summary>
        public static LogType Warning
        {
			get { return _warning; }
		}
		private static LogType _warning = new LogType("Warn", LogSeverity.Warning);

        /// <summary>
        /// Standard Error log type.
        /// </summary>
        public static LogType Error
        {
			get { return _error; }
		}
		private static LogType _error = new LogType("Err", LogSeverity.Error);

        /// <summary>
        /// Standard Debug1 log type.
        /// </summary>
        public static LogType Debug
        {
			get { return _debug; }
		}
		private static LogType _debug = new LogType("Dbg", LogSeverity.Debug1);

        /// <summary>
        /// Standard Debug2 log type.
        /// </summary>
        public static LogType ExtraDebug
        {
            get { return _extraDebug; }
        }
        private static LogType _extraDebug = new LogType("EDbg", LogSeverity.Debug2);

        /// <summary>
        /// Used for network communication logging.
        /// </summary>
        public static LogType Comm
        {
			get { return _comm; }
		}
		private static LogType _comm = new LogType("Comm", LogSeverity.Information);

		/// <summary>
		/// Used for network communication extra logging.
		/// </summary>
		public static LogType EComm
		{
			get { return _ecomm; }
		}
		private static LogType _ecomm = new LogType("Comm", LogSeverity.Debug2);

		#endregion

	}

    /// <summary>
    /// Represents an interface for log sink. Log sinks (when registered) receive
    /// log messages from the Logger.
    /// </summary>
	public interface ILogSink
	{
        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
		void Log(LogType logType, string source,  string message);


        /// <summary>
        /// Called when the sink is added to the Logger.
        /// </summary>
		void OnSinkAdded();

        /// <summary>
        /// Called when the sink is removed from the Logger.
        /// </summary>
		void OnSinkRemoved();

        /// <summary>
        /// Filter that Logger will use to determine if it should 
        /// pass the message to this sink.
        /// </summary>
        LogType.LogSeverity Filter
        {
            get;
        }
	}

    /// <summary>
    /// Base class for log sinks.
    /// </summary>
    public abstract class LogSinkBase : ILogSink {

        private LogType.LogSeverity _filter = LogType.LogSeverity.All;
        #region ILogSink Members

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
        public abstract void Log(LogType logType, string source, string message);

        /// <summary>
        /// Called when the sink is added to the Logger.
        /// </summary>
        public virtual void OnSinkAdded()
        {
        }

        /// <summary>
        /// Called when the sink is removed from the Logger.
        /// </summary>
        public virtual void OnSinkRemoved()
        {
        }

        /// <summary>
        /// Filter that Logger will use to determine if it should
        /// pass the message to this sink.
        /// </summary>
        /// <value></value>
        public virtual LogType.LogSeverity Filter
        {
            get { return _filter; }
            protected set { _filter = value; }
        }

        #endregion
    }

    /// <summary>
    /// Logs the messages to the Console.
    /// </summary>
    public class ConsoleLogSink : LogSinkBase
	{

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
        public override void Log(LogType logType, string source, string message)
        {
			Console.WriteLine(Logger.Format(logType, source, message));
		}

        public ConsoleLogSink()
        {
        }

        public ConsoleLogSink(LogType.LogSeverity filter)
        {
            this.Filter = filter;
        }
	}

    /// <summary>
    /// Logs the messages to Trace
    /// </summary>
    public class TracerLogSink : LogSinkBase
	{
        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="logType">Type of the log.</param>
        /// <param name="source">Message source.</param>
        /// <param name="message">The message.</param>
        public override void Log(LogType logType, string source, string message)
        {
            Trace.WriteLine(source + " - " + message, logType.Name);
		}

	}

	public class FileLogSink : LogSinkBase
	{

		private string _logBase = System.Reflection.Assembly.GetEntryAssembly().GetName().Name;
		private DateTime _lastMessageDate = DateTime.MinValue;
		private FileStream _fileStream;
		private StreamWriter _writer;

        public override void Log(LogType logType, string source, string message)
		{
			GetCurrentWriter().WriteLine(Logger.Format(logType, source, message));
		}

		private TextWriter GetCurrentWriter()
		{
			DateTime current = DateTime.Today;
			if (current != _lastMessageDate)
			{
				_lastMessageDate = current;
				if (_writer != null)
					_writer.Dispose();

				if (_fileStream != null)
				{
					_fileStream.Close();
					_fileStream.Dispose();
				}

				if (!Directory.Exists("Log"))
					Directory.CreateDirectory("Log");

				string fileName = Path.Combine("Log", _logBase + "_" + current.ToString("yyyy-MM-dd") + ".log");
				_fileStream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
				_fileStream.Seek(0, SeekOrigin.End);
				_writer = new StreamWriter(_fileStream);
				//_writer.AutoFlush = true;

			}
			return _writer;

		}

        
        public FileLogSink()
        {
        }

        public FileLogSink(LogType.LogSeverity filter)
        {
            this.Filter = filter;
        }

	}

}
